package client;

import java.net.*;
import java.io.*;
import java.util.*;

import messages.ClientServerChatMessage;
import messages.ServerClientChatMessage;
import messages.ServerParameters;

public class Client {

	private ObjectInputStream sInput;
	private ObjectOutputStream sOutput;
	private Socket socket;
	private ArrayList<ServerParameters> ServerList;
	private ClientGUI cg;

	private String server, username;
	private int port;

	Client(String server, int port, String username) {
		this(server, port, username, null);
	}

	Client(String server, int port, String username, ClientGUI cg) {
		this.server = server;
		this.port = port;
		this.username = username;
		this.cg = cg;
	}

	public static void main(String[] args) {
		// default values
		int portNumber = 1500;
		String serverAddress = "localhost";
		String userName = "Anonymous";

		// depending of the number of arguments provided we fall through
		switch (args.length) {
		// > javac Client username portNumber serverAddr
		case 3:
			serverAddress = args[2];
			// > javac Client username portNumber
		case 2:
			try {
				portNumber = Integer.parseInt(args[1]);
			} catch (Exception e) {
				System.out.println("Invalid port number.");
				System.out
						.println("Usage is: > java Client [username] [portNumber] [serverAddress]");
				return;
			}
			// > javac Client username
		case 1:
			userName = args[0];
			// > java Client
		case 0:
			break;
		// invalid number of arguments
		default:
			System.out
					.println("Usage is: > java Client [username] [portNumber] [serverAddress]");
			return;
		}
		// create the Client object
		Client client = new Client(serverAddress, portNumber, userName);
		// test if we can start the connection to the Server
		// if it failed nothing we can do
		if (!client.conectToServer(serverAddress, portNumber))
			return;

		// wait for messages from user
		Scanner scan = new Scanner(System.in);
		// loop forever for message from the user
		while (true) {
			System.out.print("> ");
			// read message from user
			String msg = scan.nextLine();
			// logout if message is LOGOUT
			if (msg.equalsIgnoreCase("LOGOUT")) {
				client.sendMessage(new ClientServerChatMessage(
						ClientServerChatMessage.LOGOUT, ""));
				// break to do the disconnect
				break;
			}
			// message WhoIsIn
			else if (msg.equalsIgnoreCase("WHOISIN")) {
				client.sendMessage(new ClientServerChatMessage(
						ClientServerChatMessage.WHOISIN, ""));
			} else { // default to ordinary message
				client.sendMessage(new ClientServerChatMessage(
						ClientServerChatMessage.MESSAGE, msg));
			}
		}
		// done disconnect
		client.disconnect();
	}

	public boolean conectToServer(String server, int port) {
		this.server = server;
		this.port = port;
		try {
			socket = new Socket(server, port);
		} catch (Exception ec) {
			display("Error connectiong to server:" + ec);
			return false;
		}

		String msg = "Connection accepted " + socket.getInetAddress() + ":"
				+ socket.getPort();
		display(msg);

		try {
			sOutput = new ObjectOutputStream(socket.getOutputStream());
		} catch (IOException eIO) {
			display("Exception creating new Input/output Streams: " + eIO);
			return false;
		}
		// ********* TODO Protocollo di autorizazzione ***************
		try {
			sOutput.writeObject("client");
			sOutput.writeObject(username);
		} catch (IOException eIO) {
			display("Exception doing login : " + eIO);
			disconnect();
			return false;
		}
		try {
			sInput = new ObjectInputStream(socket.getInputStream());
		} catch (IOException e1) {
			display("Exception doing login : " + e1);
		}

		ServerClientChatMessage serverListContainer = null;
		try {
			serverListContainer = (ServerClientChatMessage) sInput.readObject();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			display("Exception doing login : " + e);
		}

		this.setServerList(serverListContainer.getServerParameters());
		for (ServerParameters serv : ServerList) {
			display("> " + serv.getAddress() + " " + serv.getPort() + " "
					+ serv.getNumberOfClients());
		}

		if (connectionComplete(ServerList)) {
			new ListenFromServer().start();
			return true;
		} else {
			disconnect();
			reconnect();
			return true;
		}

		// TODO ********* Protocollo di autorizazzione ***************
	}

	public boolean connectionComplete(ArrayList<ServerParameters> arrayList) {
		for (ServerParameters serverParameters : arrayList) {
			if (serverParameters.getAddress().equalsIgnoreCase(server)
					&& serverParameters.getPort() == port) {
				if (serverParameters.getNumberOfClients() >= arrayList.get(0)
						.getNumberOfClients()) {
					return true;
				}
			}
		}
		return false;
	}

	private void display(String msg) {
		if (cg == null)
			System.out.println(msg); // println in console mode
		else
			cg.append(msg + "\n"); // append to the` ClientGUI JTextArea (or
									// whatever)
	}

	void sendMessage(ClientServerChatMessage msg) {
		try {
			sOutput.writeObject(msg);
		} catch (IOException e) {
			display("Exception writing to server: " + e);
		}
	}

	/*
	 * Provo a riconettersi a uno dei server dal elenco
	 */

	public synchronized void reconnect() {
		ArrayList<ServerParameters> tmp = new ArrayList<ServerParameters>();
		if (ServerList.size() > 1) {
			for (ServerParameters parameters : ServerList) {
				if (parameters.getAddress().equalsIgnoreCase(this.getServer())
						&& parameters.getPort() == this.getPort()) {
					// ServerList.remove(parameters);
				} else {
					tmp.add(parameters);
				}
			}
			this.setServerList(tmp);
			System.out.println(ServerList.get(0).getPort() + ""
					+ ServerList.get(0).getAddress());
			this.setPort(ServerList.get(0).getPort());
			this.setServer(ServerList.get(0).getAddress());
		}
		conectToServer(getServer(), getPort());
	}

	public String getServer() {
		return server;
	}

	public int getPort() {
		return port;
	}

	private void setServer(String server) {
		this.server = server;
	}

	private void setPort(int port) {
		this.port = port;
	}

	public synchronized ArrayList<ServerParameters> getServerList() {
		return ServerList;
	}

	public synchronized void setServerList(ArrayList<ServerParameters> serverList) {
		ServerList = serverList;
	}

	class ListenFromServer extends Thread {
		public void run() {
			while (true) {
				try {
					ServerClientChatMessage serverClientChatMessage = (ServerClientChatMessage) sInput
							.readObject();
					if (serverClientChatMessage.getMessageType() == ServerClientChatMessage.SERVER_LIST) {
						ServerList = serverClientChatMessage.getServerParameters();
						System.out.println();
						for (ServerParameters serv : ServerList) {
							System.out.println(">" + serv.getAddress() + " "
									+ serv.getPort() + " " + serv.getNumberOfClients());
						}
						System.out.print("> ");
					} else if (serverClientChatMessage.getMessageType() == ServerClientChatMessage.MESSAGE) {
						String msg = serverClientChatMessage.getMessage();
						if (cg == null) {
							System.out.println(msg);
							System.out.print("> ");
						} else {
							cg.append(msg);
						}
					}
				} catch (IOException e) {
					display("Server has close the connection: " + e);
					try {
						this.sleep(500);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
					disconnect();
					reconnect();
					if (cg != null) {
						cg.connectionFailed();
					}
					break;
				} catch (ClassNotFoundException e2) {
				}
			}
		}
	}
	
	public void disconnect() {
		try {
			if (sInput != null)
				sInput.close();
		} catch (Exception e) {
		} // not much else I can do
		try {
			if (sOutput != null)
				sOutput.close();
		} catch (Exception e) {
		} // not much else I can do
		try {
			if (socket != null)
				socket.close();
		} catch (Exception e) {
		} // not much else I can do
			// inform the GUI
		if (cg != null)
			cg.connectionFailed();
	}
}
